/**
 * @Description Ext.translate for ExtJS 2.x and 3.x; Auto translation with Google Translation API (java/php server side)
 * @author  Tomislav Milkovic
 * @license LGPLv3 http://www.opensource.org/licenses/lgpl-3.0.html
 * @version 1.5, 05.08.2009.
 */

/**
 * @updated 07.07.2009
 * @description Ext.translate.Cache object added for local caching of already
 *              translated words to minimize remote call. There is no need to
 *              call remote server every time because every component can share
 *              the same text values for translation. Instead translation will
 *              be searched from cache first then from server.
 * 
 * @updated 11.07.2009 Speed optimizations
 * @description Internal indexing by object field/property is added to optimize
 *              speed when searching/inserting new data.
 * @description Recursive AJAX calls is added for translating elements with
 *              skipping already translated data.
 * 
 * @updated 13.07.2009 Synchronous AJAX calls
 * @description Synchronous calls added when adding new text to cache enabling
 *              translation before component is rendered. Each following
 *              component will render faster if there is the same text to
 *              translate as it will be retrieved from cache.
 *              
 * @updated 20.07.2009  Events beforetranslate/aftertranslate added
 *                                              Custom translation of string/object or array of them added
 * 
 * @updated 05.08.2009  Added alphaIndex, removed search all translations in benefit of alphaIndex
 * @updated 10.08.2009  Added saving alphaIndexto cache 
 */

/**
 * Object : Ext.translate.Cache Description : Storage for data translation to
 * minimize remote calls for already existing components
 * 
 * Events : beforetranslate/aftertranslate call when field is translated while adding to cache
 *              or when all fields are translated. Sends one parameter, true - for recursive translate or false
 *                      for single field translate
 */

Ext.translate.Cache = function(){
        
        if (Ext.isObject(Ext.translate.Cache)) return Ext.translate.Cache;
        
        var object =  Ext.apply(new Ext.util.Observable (), {

               init: function() {
                        
                           this.requestStore = false;   
                           this.data = [];
                           this.fields = [];
                           this.translating = false;
                           this.alphaIndex ={};
                           this.addEvents('beforetranslate', 'aftertranslate');
                           Ext.EventManager.on(window, 'unload', this.storeToCookie,this);
                           
                           var fn = function(item,index,allItems){
                                        var name = Ext.translate.map_gui[item];
                                        this.fields[item] = [];                            
                           };
                           Ext.each(Ext.translate.map_gui,fn,this);                                     
                },
                
                        initCache : function(cfg) {
                                var conf = cfg || {}  ;
                                Ext.apply(this, conf, {
                                        autoload : true,
                                        cookieReset : false
                                });
                         
                                this.init();
                                //load prevoiusly saved translations from cookie before components is created, 
                                //this will enable faster generation of components as no remote requests needed
                
                                // if new version of application, translations will be reloaded from server,
                                // local cokies will be deleted.
                
                                // for already translated data
                                this.loadTranslations();
                
                        },
                        
                        /**
                         * Retireve translation from cache 
                         * @param  {value : 'house', language : 'CROATIAN'}
                         */
                        getTranslation : function(cfg) {

                                var fn = function(item, index, allItems) {

                                        if (item[Ext.translate.defaultLanguage] == this.value) {
                                                if (Ext.isString(item[this.language])) {
                                                        cfg.resultIDX = index;
                                                        return true;
                                                }
                                        }
                                        ;
                                };
                                Ext.each(this.data, fn, cfg);
                                return this.data[cfg.resultIDX];
                        },
                        
                        /**
                         * Retrieve translation from cache by index in array 
                         * @return object : {'CROATIAN' : 'kuaa', 'ENGLISH': 'house'}
                         */
                        getTranslationByID : function(index) {
                                if (!Ext.isNumber(index))
                                        return null;
                                if (index >= this.data.length)
                                        return null;
                                return this.data[index];
                        },
                        

                        /**
                         * Retrieve translation from cache by index and language 
                         * @return object : {'CROATIAN' : 'kuaa', 'ENGLISH': 'house'}
                         */
                        getTranslationByIDlanguage : function(index, language) {
                                if (!Ext.isNumber(index ))
                                        return null;
                                if (index >= this.data.length)
                                        return null;
                                var obj = this.getTranslationByID(index);
                                return obj[language];
                        },
                        

                        /**
                         * Store translation to cache Config object : {value :'house', language : 'CROATIAN' , field : 'fieldLabel'} 
                         *              value - text to translate 
                         *              language - language to translate from language defined in Ext.translate.defaultLanguage
                         *              field - field/object property for which translation will be done, used for internal inedxing
                         *              to get more speed in future searches 
                         *				Return Value : position in cache so default value can be attatched to Component property.
                         *          	Many objects can have the same field name with different text values, thus enabling field indexing.
                         *              Also alpha indexing is done for cookie storage ,and cache translation searh by text values.
                         */
                        setTranslation : function(cfg,callback) {
                        	 
                        	 if (!Ext.translate.isInitialized()) return;
                        	 
                                cfg.found = false;
                                if(!defined(cfg.value)) return;

                                var fn = function(item, index, allItems) {

                                        if (cfg.byField) {
                                                if (Ext.translate.Cache.data[id] == this.value) {
                                                        this.found = true;
                                                        this.id = index;
                                                        return true;
                                                }
                                                ;
                                        } else if (item[Ext.translate.defaultLanguage] == this.value) {
                                                this.found = true;
                                                this.id = index;
                                                return true;
                                        }
                                        ;
                                };
                                
                                var fnAlphaIndex = function(item, index, allItems){
                                        var obj = Ext.translate.Cache.data[item];
                                        if(Ext.isObject(obj) &&  (this.value == obj[Ext.translate.defaultLanguage])){
                                                this.found = true;
                                                this.id = item;
                                                return true;
                                        }                                       
                                };
                                
                                //search by indexed field
                                if (Ext.isString(cfg.field)){
                                        cfg.byField = true;
                                        Ext.each(this.fields[cfg.field], fn, cfg);      
                                };
                                

                                //search by alpha index if defined 
                                if ((!cfg.found) && defined(this.alphaIndex[cfg.value[0]])){                                                    
                                        cfg.byField = false;
                                        Ext.each(this.alphaIndex[cfg.value[0]], fnAlphaIndex, cfg);
                                };                              
                                
//                              //search all cache
//                              if (!cfg.found) {
//                                      cfg.byField = false;
//                                      Ext.each(this.data, fn, cfg);
//                              };

                                if (!cfg.found) {
                                        var obj = {};
                                        obj[Ext.translate.defaultLanguage] = cfg.value;
                                        cfg.id = this.data.length;
                                        this.data[cfg.id] = obj;
                                        
                                        //text could be translated without field
                                        if (Ext.isString(cfg.field)){
                                                var flist = this.fields[cfg.field];
                                                if (!defined(flist)) {
                                                        this.fields[cfg.field] = [];
                                                        flist = this.fields[cfg.field];
                                                }
                                                flist[flist.length] = this.data.length - 1;
                                        };
                                        
                                        this.addToAlphaIndex(cfg);

                                        if (!Ext.translate.Manager.isLanguageChangedToDef())
                                                this.translate(obj,callback);
                                        return cfg.id;
                                } else {
                                        return cfg.id;
                                }
                        },
                        

                        // @param - object {value:''} - contains default language value to be indexed
                        addToAlphaIndex : function(cfg){
                                
                                var fnAidx = function(item, index, allItems){
                                                var obj = Ext.translate.Cache.data[item];
                                                if(Ext.isObject(obj)) cfg.found = this.value == obj[Ext.translate.defaultLanguage];     
                                };
                                
                                if(!defined(this.alphaIndex[cfg.value[0]])){
                                        this.alphaIndex[cfg.value[0]] = [];
                                        this.alphaIndex[cfg.value[0]].push(cfg.id);     
                                        return;
                                };
                                
                                Ext.each(this.alphaIndex[cfg.value[0]], fnAidx, cfg);
                                if (!cfg.found){
                                        this.alphaIndex[cfg.value[0]].push(cfg.id);     
                                }                               
                                
                        },
                        
                        rebuildAlphaIndex :function(){
                                
                                var fnAidx = function(item, index, allItems){
                                        var obj = {'value' : item[Ext.translate.defaultLanguage], id:index};
                                        this.addToAlphaIndex(obj);
                                };
                        
                                Ext.each(this.data, fnAidx, this);
                        },
                        
            /**
             * Translates custom Array/Object
             * @params Receives String, Object or Array of one of them
             * @params options
             *                  fields - array of fields to translate if object or array of objects are passes in first parameter
             *                  calback - function to be called after translation
             *                  scope   - scope of the function
             *                  params  - any additional parameters to add to the callback function
             *                  replace - boolean, if true, original text will be replaced; only if object passed for translation
             */
            setTranslationCustom : function(data, o){
            	 if (!Ext.translate.isInitialized()) return;

                  var isArray = Ext.isArray(data);
                  
                  //translate single string/object
                  var fn = function(data, o){
                                  
                                         //store string to cache and link field name with cache index 
                                          if (Ext.isObject(data)){
                                                  if (!Ext.isArray(o.fields)) return;
                                                  if (!defined(data.translateStore)) data.translateStore=[];
                                                  
                                           var arrayFn = function(item,index,allItems){
                                                     var obj = {value : data[item], language : Ext.translate.currentLanguage, field : item };
                                                                 data.translateStore[item] =  Ext.translate.Cache.setTranslation(obj,o);
                                                                 if (o.replace===true){
                                                                         var tran = Ext.translate.Cache.data[data.translateStore[item]];
                                                                         data[item] = tran[Ext.translate.currentLanguage];
                                                                 };
                                           };                                             
                                                   Ext.each(o.fields,arrayFn);
                                                                                                  
                                          };
                                          
                                          //just store string to cache
                                          if (Ext.isString(data)){
                                                  var obj = {value : data, language : Ext.translate.currentLanguage };
                                                  Ext.translate.Cache.setTranslation(obj,o);
                                          };
                                          
                                          return;
                  };

                        //if not array  
                    if (!isArray){
                        fn(data,o);
                        return ;  
                    };
                  
                    //translate array of string/object              
                   var arrayFn = function(item,index,allItems){
                          if (Ext.isString(item) || Ext.isObject(item)) fn(item,o);
                   };
                   Ext.each(data,arrayFn);                      
                        
                  
            },
                        
                        
                        /**
                         * Build params object for AJAX call. Depends on clientonly or client-server version
                         */
                        getAjaxParams : function(text) {
                                var params_;

                                if (Ext.translate.clientonly == true) {
                                        var pair = Ext.translate.Manager.getLanguagePair(Ext.translate.defaultLanguage,Ext.translate.currentLanguage);
                                        params_ = Ext.translate.Manager.getGTArequest(pair, text);
                                } else {
                                        params_ = {
                                                'cmd' : 'translate',    
                                                'translate' : text,
                                                'language' : Ext.translate.currentLanguage
                                        };
                                }
                                ;
                                return params_;
                        },
                        

                        /**
                         * Call remote translation service for existing object. Translation will be
                         * requested for language defined in Ext.translate.defaultLanguage
                         * 
                         * @param obj - { 'CROATIAN' : 'kuaa', 'ENGLISH': 'house'}
                         * @param callback -
                         *            object containing parameters to callback return to caler {callback :
                         *            '', scope :'' , params : []) function - to be called scope - in
                         *            context of specified object with additional parameters
                         */
                        translate : function(obj, callbackcfg) {

                                 
                                if (Ext.translate.Manager.isLanguageChangedToDef() || (!Ext.translate.Manager.isLanguageChanged())){                                    
                                        return false;
                                };
                                        

                                var callbackFn = function(options, success, response) {

                                        if (success) {
                                                if (Ext.translate.clientonly == true) {                                                 
                                                         //this if..clause is fixup due to some error in 3rd party Ext.lib.Ajax(should return success:false)                                            
                                                        if (response.responseObject.responseStatus==200)
                                                                this[Ext.translate.currentLanguage] = response.responseObject.responseData.translatedText || Ext.decode(response.responseText);
                                                } else
                                                        this[Ext.translate.currentLanguage] = response.responseText;
                                                //Ext.translate.Cache.addToAlphaIndex({value : this[Ext.translate.currentLanguage] });
                                        };
                                        
                                        var obj = options.callbackParams;
                                        if (!Ext.isObject(obj))return true;
                                        try {
                                                var p = [this].concat(obj.params);                              
                                                obj.callback.apply(obj.scope,p);
                                        } catch (ex) {};                

                                        // delete updating status
                                        delete this[Ext.translate.currentLanguage + '_S'];
                                        Ext.translate.Cache.fireEvent('aftertranslate', this,false);
                                        
                                };

                                Ext.translate.Cache.fireEvent('beforetranslate', this,false);
                                
                                // set updating status
                                // component can wait until updating is finnished
                                obj[Ext.translate.currentLanguage + '_S'] = true;

                                var parm = this.getAjaxParams(obj[Ext.translate.defaultLanguage]);

                                // calls for local domain - synchronously
                                if (Ext.translate.clientonly == false) {
                                        this.translateSAJAX( {
                                                debug : Ext.translate.debug,
                                                'url' : Ext.translate.URL,
                                                'params' : parm,
                                                //callback : callbackFn.createDelegate(obj,[callFn],true)
                                                'callback' : callbackFn,
                                                'callbackParams' : callbackcfg,
                                                'scope' : obj
                                        });
                                        return true;
                                }

                                //calls for JSONP for remote domain
                                Ext.Ajax.request( {
                                        url : Ext.translate.URL,
                                        scriptTag : true,
                                        debug : Ext.translate.debug,
                                        params : parm,
                                        method : 'GET',
                                        //callback : callbackFn.createDelegate(obj,[callFn],true),
                                        'callback' : callbackFn,
                                        'callbackParams' : callbackcfg,
                                        scope : obj
                                });

                                return true;
                        },
                        
                        /**
                         * Translate to currently selected language. Optionally, force retranslation of
                         * cached data. Translation will be requested for language defined in
                         * Ext.translate.defaultLanguage
                         * 
                         * @param obj - {'CROATIAN' : 'kuaa', 'ENGLISH': 'house'}
                         * @param callbackFn -
                         *            callback function called after all translation is done. Next AJAX
                         *            call is made after previous is finished. Caller of this function
                         *            will not wait.
                         */
                        translateAll : function(callbackFn, forceTranslate) {

                                if (this.translating)
                                        return false;

                                this.fireEvent('beforetranslate', this,true);
                                
                                var force = forceTranslate;

                                var callFn = function(options, success, response) {
                                        var cb = options.callbackParams;
                                        if (success == true) {
                                                if (Ext.translate.clientonly == true) {
                                                 //this if..clause is fixup due to some error in 3rd party Ext.lib.Ajax(should return success:false)  
                                                 if (response.responseObject.responseStatus==200)
                                                        cb.scope[Ext.translate.currentLanguage] = response.responseObject.responseData.translatedText || Ext.decode(response.responseText);
                                                } else
                                                        cb.scope[Ext.translate.currentLanguage] = response.responseText;
                                                //this.addToAlphaIndex({value : cb.scope[Ext.translate.currentLanguage]});
                                        }

                                        //delete updating status
                                        delete cb.scope[Ext.translate.currentLanguage + '_S'];
                                        var index = cb.params[0] + 1;
                                        if (index == this.data.length) {
                                                if (Ext.isFunction( cb.callback ))
                                                        cb.callback();
                                                this.translating = false;
                                                this.requestStore = true;
                                                this.fireEvent('aftertranslate', this,true);
                                                return;
                                        }
                                        ;
                                        translateFn.call(this, index);

                                };

                                var translateFn = function(index) {

                                        if (!force) {
                                                if (defined(this.data[index][Ext.translate.currentLanguage])) {
                                                        index = index + 1;
                                                        if (index == this.data.length) {
                                                                this.translating = false;
                                                                if (Ext.isFunction(callbackFn))
                                                                        callbackFn();
                                                                this.requestStore = true;
                                                                this.fireEvent('aftertranslate', this,true);
                                                                return;
                                                        }
                                                        translateFn.call(this, index);
                                                        return;
                                                }
                                        }
                                        ;

                                        // set updating status
                                        // component can wait until updating is finished
                                        this.data[index][Ext.translate.currentLanguage + '_S'] = true;

                                        var parm = this.getAjaxParams(this.data[index][Ext.translate.defaultLanguage]);
                                        Ext.Ajax.request( {
                                                url : Ext.translate.URL,
                                                debug : Ext.translate.debug,
                                                params : parm,
                                                method : 'GET',
                                                scriptTag : Ext.translate.clientonly,
                                                callback : callFn,
                                                callbackParams : { scope : this.data[index], params: [index], callback : callbackFn},
                                                scope : this
                                        });

                                };

                                this.translating = true;
                                translateFn.call(this, 0);

                        },
                        
                        /**
                         * Clears translation from cache for specified language Config object :
                         * {language : 'CROATIAN', resetCookie : true}
                         */
                        resetTranslation : function(cfg) {

                                var fn = function(item, index, allItems) {
                                        //language
                                        if (defined(item[this.language])) {
                                                delete item[this.language];
                                        }
                                        ;
                                };
                                Ext.each(this.data, fn, cfg);

                                if (cfg.resetCookie)
                                        this.requestStore=true;
                        },
                
                        
                        stringToArray : function(data, size){
                        
                        	var d = new Array();
                        	var i =0;
                        	var counter=0;
                        	var tmp='';
                        	while (counter<data.length){
                        		tmp = tmp + data[counter];
                        		i++;
                        		
                        		if((i==size)||(data.length==counter+1)){
                        			i=0;
                        			d.push(tmp);
                        			tmp='';
                        		};
                        		counter++;
                        	};
                        	 return d;
                        },
                        
                        /**
                         * Store translation to cookie to reload on next page/session load
                         */
                         storeToCookie : function() {
                               if (!Ext.translate.Cache.requestStore) return;

                               if (!Ext.isString(Ext.translate.Cache.id))
                                        return;
                               
                               this.storeToCookieArray(Ext.translate.Cache.data);

                         },

                         storeToCookieArray : function(data,alpha) {

                                var tmp = data.slice();

                                var fn = function(item, index, allItems) {

                                        for (prop in item) {
                                                if ((prop == Ext.translate.defaultLanguage) || (prop == Ext.translate.currentLanguage)) {
                                                } else
                                                        delete item[prop];
                                        }
                                };
                                Ext.each(tmp, fn);

                                var json = Ext.util.JSON.encode(tmp);
                                var cmpr = compress(json, 4096);
                                var arry = this.stringToArray(cmpr,4000);
                                
                                var fn2 = function(item, index, allItems) {
                                	Ext.translate.Cookie.set(Ext.translate.Cookie.prefix + Ext.translate.currentLanguage+ Ext.translate.Cache.id + index, item);
                                };                                
                                Ext.each(arry, fn2);
                                
                                Ext.translate.Cookie.set(Ext.translate.Cookie.prefix + Ext.translate.currentLanguage + Ext.translate.Cache.id + '_items', arry.length);
                                
                                json = undefined;
                                cmpr = undefined;
                        },
                        
                        /**
                         * Load translation from cookie to reload on next page/session load.
                         */
                        loadFromCookie : function() {

                                if (!Ext.isBoolean(this.autoload))
                                        return;
                                if (!Ext.isString(this.id))
                                        return;

                                var items = Ext.translate.Cookie.get(Ext.translate.Cookie.prefix + Ext.translate.currentLanguage + Ext.translate.Cache.id + '_items');
                                if (!Ext.isNumber(items)){ 
                                	return;
                                }
                                
                                var i =0;
                                var dataPart ='';
                                
                                while(i<items){
                                	var str = Ext.translate.Cookie.get(Ext.translate.Cookie.prefix +  Ext.translate.currentLanguage + Ext.translate.Cache.id + i);
                                	if (!Ext.isString(str)){
                                    	return;                                		
                                	}
                                	dataPart = dataPart + str;
                                	i++;
                                }
                                

                                        var data = decompress(dataPart);
                                        data = Ext.util.JSON.decode(data);
                                        if (Ext.isArray(data)) {
                                                this.data = data;
                                        } else {
                                        	return;                                         	
                                        }
                                

                                this.rebuildAlphaIndex();
                                str = undefined;
                                data = undefined;
                                dataPart = undefined;
                        },

                        /**
                         * Load translation from server for currently selected language (synchronous
                         * AJAX call)
                         */
                        loadFromServer : function() {

                                if (!Ext.translate.Manager.isLanguageChangedToDef())
                                        return;
                                if (Ext.translate.clientonly == true)
                                        return;

                                var callFn = function(options, success, response) {
                                        if (success) {
                                                var data = Ext.util.JSON.decode(response.responseText);

                                                if (Ext.isArray(data)) {
                                                        this.data = data;
                                                }
                                        } else
                                                this.loadFromCookie();

                                };

                                this.translateSAJAX( {
                                        url : Ext.translate.URL,
                                        params : {
                                                'cmd' : 'load' ,
                                                'language' : Ext.translate.currentLanguage
                                        },
                                        callback : callFn,
                                        scope : this
                                });
                        },
                        
                        /**
                         * Determine location for load existing translations cookie/server side
                         */
                        loadTranslations : function() {

                                if (this.autoload != true)
                                        return;

                                var ver = Ext.translate.Cookie.get(Ext.translate.Cookie.version);
                                if (Ext.translate.version != ver) {
                                        if (Ext.translate.clientonly != true)
                                                this.loadFromServer();
                                        Ext.translate.Cookie.set(Ext.translate.Cookie.version,  Ext.translate.version);
                                        Ext.translate.Cookie.clear(Ext.translate.Cookie.prefix + this.id);
                                        return;
                                }
                                ;

                                if (Ext.translate.clientonly == true) {
                                        this.loadFromCookie();
                                        return;
                                }
                                ;

                                if (Ext.isBoolean(this.resetCookies)) {
                                        this.loadFromServer();
                                } else
                                        this.loadFromCookie();
                        },

                        translateSAJAX : function(o) {
                                var bComplete = false;
                                if (window.XMLHttpRequest) {
                                        AJAX = new XMLHttpRequest();
                                } else {
                                        AJAX = new ActiveXObject("Microsoft.XMLHTTP");
                                }
                                if (!AJAX)
                                        return false;

                                var url = Ext.urlAppend(o.url, Ext.urlEncode(o.params));
                                AJAX.open("GET", url, false);
                                AJAX.send(null);
                                if (Ext.isFunction(o.callback)) {
                                        var status = ((AJAX.readyState == 4) && (AJAX.status == 200) && (AJAX.responseText.length > 2));
                                        var obj = {
                                                responseText : AJAX.responseText,
                                                status : AJAX.status
                                        };
                                        return o.callback ? o.callback.apply(o.scope, [ o, status, obj ])
                                                        : null;
                                };
                        }                       
        });
        
        return object;
}();